package com.angel.bo.business.plugin;

import com.angel.bo.common.util.JsonSerializeUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Properties;

/**
 * SQL监控，Mybatis插件
 * <p>默认情况下，MyBatis 允许使用插件来拦截的方法调用包括：</p>
 * <ul><li>拦截执行器的方法，Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)</li>
 * <li>拦截参数的处理，ParameterHandler (getParameterObject, setParameters)</li>
 * <li>拦截结果集的处理，ResultSetHandler (handleResultSets, handleOutputParameters)</li>
 * <li>拦截Sql语法构建的处理，StatementHandler (prepare, parameterize, batch, update, query)</li></ul>
 */
@Intercepts(value = {
        @Signature (type=Executor.class,
                method="update",
                args={MappedStatement.class,Object.class}),
        @Signature(type=Executor.class,
                method="query",
                args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,
                        CacheKey.class,BoundSql.class}),
        @Signature(type=Executor.class,
                method="query",
                args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})
public class SQLMonitorPlugin implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(SQLMonitorPlugin.class);
    //是否监控显示SQL
    private static final boolean SHOWSQL = true;
    //慢SQL时间阀值，单位毫秒
    private static final int SLOWER = 3000;
    //大集合监控阀值，单位条
    private static final int MAXCOUNT = 80;

    public SQLMonitorPlugin() {
    }

    /**
     * 描述   : MyBatis的SQL监控插件拦截器
     * 联系人 : 张政<zhang_zheng@sinosoft.com.cn>
     * 时间   :2017/8/2
     *
     * @param invocation 调用方法
     * @return Object
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        //请求参数
        Object parameter = null;
        if (1 < invocation.getArgs().length) {
            parameter = invocation.getArgs()[1];
        }
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        //执行的sql所在的mapper文件
        String resource = mappedStatement.getResource();
        //执行sql的dao文件的包名+方法名
        String daoMethod = mappedStatement.getId();
        //去除sql文中的换行
        sql = sql.replace("\n", "").replaceAll("\\s+", " ");
        if (SHOWSQL) {
            LOGGER.warn("[SQLMonitorPlugin]SQL监控：{}|{}，执行SQL：{}，参数：{}", resource, daoMethod,
                    sql, JsonSerializeUtil.toJson(parameter));
        }
        try {
            long start = System.currentTimeMillis();
            Object e = invocation.proceed();
            long timeTicket = System.currentTimeMillis() - start;
            if (SLOWER < timeTicket) {
                LOGGER.warn("[SQLMonitorInterceptor]SQL监控：{}|{}，慢SQL（{}/{}ms）{}，参数：{}",
                        resource, daoMethod, timeTicket, SLOWER, sql, JsonSerializeUtil.toJson(parameter));
            }
            if (e instanceof Collection) {
                int ct = ((Collection<?>) e).size();
                if (MAXCOUNT < ct) {
                    LOGGER.warn("[SQLMonitorInterceptor]SQL监控：{}|{}，大集合（{}/{}）{}，参数：{}",
                            resource, daoMethod, ct, MAXCOUNT, sql, JsonSerializeUtil.toJson(parameter));
                }
            }
            return e;
        } catch (Exception e) {
            LOGGER.warn("[SQLMonitorPlugin]SQL监控：{}|{}，执行出错：{}，参数：{}", resource, daoMethod,
                    sql, JsonSerializeUtil.toJson(parameter), e);
            throw e;
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

}
